#include "util.h"
#include <cstdio>
#include <vector>
#include <assert.h>
using std::vector;

namespace util
{
	CGeometry::CGeometry()
	{
		clear();
	}
	CGeometry::~CGeometry()
	{
		clear();
	}
	void CGeometry::create_cube(void)
	{
		clear();
		
		m_pv = new float[24 * 3];
		m_pn = new float[24 * 3];
		m_pt = new float[24 * 2];
		m_pidx = new short[12 * 3];
		m_nidx = 12 * 3;
		
		int idx = 0;
		#define V(op1, op2, op3)   m_pv[idx + 0] = op1 0.5f, m_pv[idx + 1] = op2 0.5f, m_pv[idx + 2] = op3 0.5f, idx += 3
		/*-0.5f, -0.5f, -0.5f,*/	V(-, -, -);
		/*-0.5f, -0.5f,  0.5f,*/	V(-, -, +);
		/*0.5f, -0.5f,  0.5f,*/		V(+, -, +);
		/*0.5f, -0.5f, -0.5f,*/		V(+, -, -);
		/*-0.5f,  0.5f, -0.5f,*/	V(-, +, -);
		/*-0.5f,  0.5f,  0.5f,*/	V(-, +, +);
		/*0.5f,  0.5f,  0.5f,*/		V(+, +, +);
		/*0.5f,  0.5f, -0.5f,*/		V(+, +, -);
		/*-0.5f, -0.5f, -0.5f,*/	V(-, -, -);
		/*-0.5f,  0.5f, -0.5f,*/	V(-, +, -);
		/*0.5f,  0.5f, -0.5f,*/		V(+, +, -);
		/*0.5f, -0.5f, -0.5f,*/		V(+, -, -);
		/*-0.5f, -0.5f, 0.5f,*/		V(-, -, +);
		/*-0.5f,  0.5f, 0.5f,*/		V(-, +, +);
		/*0.5f,  0.5f, 0.5f, */		V(+, +, +);
		/*0.5f, -0.5f, 0.5f,*/		V(+, -, +);
		/*-0.5f, -0.5f, -0.5f,*/	V(-, -, -);
		/*-0.5f, -0.5f,  0.5f,*/	V(-, -, +);
		/*-0.5f,  0.5f,  0.5f,*/	V(-, +, +);
		/*-0.5f,  0.5f, -0.5f,*/	V(-, +, -);
		/*0.5f, -0.5f, -0.5f,*/		V(+, -, -);
		/*0.5f, -0.5f,  0.5f,*/		V(+, -, +);
		/*0.5f,  0.5f,  0.5f,*/		V(+, +, +);
		/*0.5f,  0.5f, -0.5f,*/		V(+, +, -);
		#undef V
		
		idx = 0;
		#define N(a, b, c)   m_pn[idx + 0] = a, m_pn[idx + 1] = b, m_pn[idx + 2] = c, idx += 3
		N(0, -1, 0);   N(0, -1, 0);   N(0, -1, 0);   N(0, -1, 0);
		N(0, 1, 0);    N(0, 1, 0);    N(0, 1, 0);    N(0, 1, 0);
		N(0, 0, -1);   N(0, 0, -1);   N(0, 0, -1);   N(0, 0, -1);
		N(0, 0, 1);    N(0, 0, 1);    N(0, 0, 1);    N(0, 0, 1);
		N(-1, 0, 0);   N(-1, 0, 0);   N(-1, 0, 0);   N(-1, 0, 0);
		N(1, 0, 0);    N(1, 0, 0);    N(1, 0, 0);    N(1, 0, 0);
		#undef N
		
		idx = 0;
		#define T(s, t)   m_pt[idx + 0] = s, m_pt[idx + 1] = t, idx += 2
		T(0, 0);   T(0, 1);   T(1, 1);   T(1, 0);
		T(1, 0);   T(1, 1);   T(0, 1);   T(0, 0);
		T(0, 0);   T(0, 1);   T(1, 1);   T(1, 0);
		T(0, 0);   T(0, 1);   T(1, 1);   T(1, 0);
		T(0, 0);   T(0, 1);   T(1, 1);   T(1, 0);
		T(0, 0);   T(0, 1);   T(1, 1);   T(1, 0);
		#undef T
		
		idx = 0;
		#define I(a, b, c)   m_pidx[idx + 0] = a, m_pidx[idx + 1] = b, m_pidx[idx + 2] = c, idx += 3
		I(0, 2, 1);
		I(0, 3, 2);
		I(4, 5, 6);
		I(4, 6, 7);
		I(8, 9, 10);
		I(8, 10, 11);
		I(12, 15, 14);
		I(12, 14, 13);
		I(16, 17, 18);
		I(16, 18, 19);
		I(20, 23, 22);
		I(20, 22, 21);
		#undef I
	}
	void CGeometry::create_sphere(int slices)
	{
		clear();
	
		int i, j;
		int num_parallels = slices / 2;
		int nvts = (num_parallels + 1) * (slices + 1);
		m_nidx = num_parallels * slices * 6;
		float angle_step = (2.0f * PI) / ((float)slices);
	
		m_pv = new float[3 * nvts];
		m_pn = new float[3 * nvts];
		m_pt = new float[2 * nvts];
		m_pidx = new short[m_nidx];
	
		for (i=0; i<num_parallels+1; i++) {
			for (j=0; j<slices+1; j++) {
				// fill vertex position data
				int v = (i * (slices + 1) + j) * 3;
				m_pv[v + 0] = sinf(angle_step * (float)i) * sinf(angle_step * (float)j);
				m_pv[v + 1] = cosf(angle_step * (float)i);
				m_pv[v + 2] = sinf(angle_step * (float)i) * cosf(angle_step * (float)j);
				// fill normal data
				m_pn[v + 0] = m_pv[v + 0];
				m_pn[v + 1] = m_pv[v + 1];
				m_pn[v + 2] = m_pv[v + 2];
				// fill texture coord data
				int ti = (i * (slices + 1) + j) * 2;
				m_pt[ti + 0] = (float)j / (float)slices;
				m_pt[ti + 1] = (1.0f - (float)i) / (float)(num_parallels - 1);
			}
		}
		// fill vertex index data
		int ii = 0;
		for (i=0; i<num_parallels; i++) {
			for (j=0; j<slices; j++) {
				m_pidx[ii + 0] = i * (slices + 1) + j;
				m_pidx[ii + 1] = (i + 1) * (slices + 1) + j;
				m_pidx[ii + 2] = (i + 1) * (slices + 1) + (j + 1);
			
				m_pidx[ii + 3] = i * (slices + 1) + j;
				m_pidx[ii + 4] = (i + 1) * (slices + 1) + (j + 1);
				m_pidx[ii + 5] = i * (slices + 1) + (j + 1);
				ii += 6;
			}
		}
	}
	void CGeometry::create_torus(int precision)
	{
		clear();
		
		int nvts = (precision + 1) * (precision + 1);   // vertex number
		m_nidx = 2 * precision * precision * 3;

		// allocate some tmp data
		vec3* pverts = new vec3[nvts];
		vec3* pnors = new vec3[nvts];
		vec2* ptexs = new vec2[nvts];
		
		// the first ring , inner radius is 4 , outer radius is 1.5
		for (int i=0; i<precision+1; i++)
		{
			// vertex data
			vec3 o = v3rotZ(v3gen(1.5f, 0.0f, 0.0f), i * 360.0f / precision);
			vec3 rad = v3gen(4.0f, 0.0f, 0.0f);
			pverts[i] = v3add(o, rad);
			// normal data
			vec3 tangent_s = v3gen(0.0f, 0.0f, -1.0f);
			vec3 tangent_t = v3rotZ(v3gen(0.0f, -1.0f, 0.0f), i * 360.0f / precision);
			pnors[i] = v3cross(tangent_t, tangent_s);
			// texcoord data
			ptexs[i] = v2gen(0.0f, (float)i / precision);
		}
		// rotate to get other rings
		for (int ring=1; ring<precision+1; ring++)
		{
			for (int i=0; i<precision+1; i++)
			{
				int idx = ring * (precision + 1) + i;
				// vertex data
				pverts[idx] = v3rotY(pverts[i], ring * 360.0f / precision);
				// normal data
				pnors[idx] = v3rotY(pnors[i], ring * 360.0f / precision);
				// texcoord data
				ptexs[idx].x = 2.0f * ring / precision;
				ptexs[idx].y = ptexs[i].y;
			}
		}
		
		// calculate the indices
		m_pidx = new short[m_nidx];
		for (int ring=0; ring<precision; ring++)
		{
			for (int i=0; i<precision; i++)
			{
				m_pidx[((ring*precision+i)*2)*3+0]=ring*(precision+1)+i;
				m_pidx[((ring*precision+i)*2)*3+1]=(ring+1)*(precision+1)+i;
				m_pidx[((ring*precision+i)*2)*3+2]=ring*(precision+1)+i+1;
				m_pidx[((ring*precision+i)*2+1)*3+0]=ring*(precision+1)+i+1;
				m_pidx[((ring*precision+i)*2+1)*3+1]=(ring+1)*(precision+1)+i;
				m_pidx[((ring*precision+i)*2+1)*3+2]=(ring+1)*(precision+1)+i+1;
			}
		}
		
		// allocate data for OpenGL array
		m_pv = new float[3 * nvts];
		m_pn = new float[3 * nvts];
		m_pt = new float[2 * nvts];
		// copy vertex to raw vertex array
		int idx = 0;
		for (int i=0; i<nvts; i++)
		{
			m_pv[idx + 0] = pverts[i].x;
			m_pv[idx + 1] = pverts[i].y;
			m_pv[idx + 2] = pverts[i].z;
			idx += 3;
		}
		// copy normal to raw normal array
		idx = 0;
		for (int i=0; i<nvts; i++)
		{
			m_pn[idx + 0] = pnors[i].x;
			m_pn[idx + 1] = pnors[i].y;
			m_pn[idx + 2] = pnors[i].z;
			idx += 3;
		}
		// copy texcoord to raw texcoord array
		idx = 0;
		for (int i=0; i<nvts; i++)
		{
			m_pt[idx + 0] = ptexs[i].x;
			m_pt[idx + 1] = ptexs[i].y;
			idx += 2;
		}

		delete[] pverts;
		delete[] pnors;
		delete[] ptexs;
	}
	void CGeometry::create_floor()
	{
		assert(false);
		/*
		const int GRID = 64;      // grid density
		int nvts = GRID * GRID;   // vertex number
		m_pv = new float[3 * nvts];
		m_pn = new float[3 * nvts];
		m_pt = new float[2 * nvts];
		
		for (int i=0; i<GRID; i++)
		{
			for (int j=0; j<GRID; j++)
			{
				// fill vertex data
				int idx = i * GRID + j;
				m_pv[idx + 0] = ((float)i / (GRID - 1)) * 2 - 1;
				m_pv[idx + 1] = 0.0f;
				m_pv[idx + 2] = ((float)j / (GRID - 1)) * 2 - 1;
				// fill normal data
				m_pn[idx + 0] = 0.0f;
				m_pn[idx + 1] = 1.0f;
				m_pn[idx + 2] = 0.0f;
				// fill texcoord data
				m_pt[idx + 0] = (float)i / (GRID - 1);
				m_pt[idx + 1] = -(float)j / (GRID - 1);
			}
		}
		
		m_pidx = new short[2 * GRID * (GRID - 1)];
		for (int i=0; i<GRID-1; i++)
		{
			for (int j=0; j<GRID; j++)
			{
				m_pidx[(i * GRID + j) * 2 + 0] = (i + 1) * GRID + j;
				m_pidx[(i * GRID + j) * 2 + 1] = i * GRID + j;
			}
		}
		*/
	}
	const float* CGeometry::get_vertex(void) const
	{
		return m_pv;
	}
	const float* CGeometry::get_normal(void) const
	{
		return m_pn;
	}
	const float* CGeometry::get_texcoord(void) const
	{
		return m_pt;
	}
	const short* CGeometry::get_index_data(void) const
	{
		return m_pidx;
	}
	int CGeometry::get_index_num(void) const
	{
		return m_nidx;
	}
	void CGeometry::clear(void)
	{
		if (m_pv)     { delete[] m_pv;   m_pv = NULL; }
		if (m_pn)     { delete[] m_pn;   m_pn = NULL; }
		if (m_pt)     { delete[] m_pt;   m_pt = NULL; }
		if (m_pidx)   { delete[] m_pidx; m_pidx = NULL; }
		m_nidx = 0;
	}
	CObj::CObj()
	{
		unload();
	}
	CObj::~CObj()
	{
		unload();
	}
	bool CObj::load(const char* file)
	{
		unload();
		
		float x, y, z;
		vector<float> vv;   // vertex
		vector<float> vn;   // normal
		vector<float> vt;   // texcoord
		vector<int>   vvi;  // vertex index
		vector<int>   vni;  // normal index
		vector<int>   vti;  // texcoord index
		
		bool has_texcorrd = false;
		bool has_normal = false;
		
		FILE* pf = fopen(file, "r");
		if (!pf)   return false;
		
		while (true) {
			char head[128];
			if (fscanf(pf, "%s", head) == EOF)   break;
			
			if (0 == strcmp(head, "v")) {
				fscanf(pf, "%f %f %f\n", &x, &y, &z);
				vv.push_back(x);
				vv.push_back(y);
				vv.push_back(z);
			} else if (0 == strcmp(head, "vn")) {
				fscanf(pf, "%f %f %f\n", &x, &y, &z);
				vn.push_back(x);
				vn.push_back(y);
				vn.push_back(z);
				has_normal = true;
			} else if (0 == strcmp(head, "vt")) {
				fscanf(pf, "%f %f\n", &x, &y);
				vt.push_back(x);
				vt.push_back(y);
				has_texcorrd = true;
			} else if (0 == strcmp(head, "f")) {
				if (has_texcorrd && has_normal) {
					// f int/int/int int/int/int int/int/int --- vertex / texcoord / normal index
					int v[3] = {0};
					int t[3] = {0};
					int n[3] = {0};
					
					if (9 != fscanf(pf, "%d/%d/%d %d/%d/%d %d/%d/%d\n", &v[0], &t[0], &n[0], &v[1], &t[1], &n[1], &v[2], &t[2], &n[2]))
						continue;
					
					vvi.push_back(v[0] - 1);
					vti.push_back(t[0] - 1);
					vni.push_back(n[0] - 1);
					vvi.push_back(v[1] - 1);
					vti.push_back(t[1] - 1);
					vni.push_back(n[1] - 1);
					vvi.push_back(v[2] - 1);
					vti.push_back(t[2] - 1);
					vni.push_back(n[2] - 1);
				} else if (has_texcorrd && !has_normal) {
					// todo...
					assert(false);
				} else if (!has_texcorrd && has_normal) {
					// f int//int int//int int//int --- vertex // normal index
					int v[3] = {0};
					int n[3] = {0};
					
					if (6 != fscanf(pf, "%d//%d %d//%d %d//%d\n", &v[0], &n[0], &v[1], &n[1], &v[2], &n[2]))
						continue;
					
					vvi.push_back(v[0] - 1);
					vni.push_back(n[0] - 1);
					vvi.push_back(v[1] - 1);
					vni.push_back(n[1] - 1);
					vvi.push_back(v[2] - 1);
					vni.push_back(n[2] - 1);
				} else {
					// todo...
					assert(false);
				}
			}
		}
		
		fclose(pf);
		
		// bulid render data based on index data
		int vlen = vvi.size();
		int nlen = vni.size();
		int tlen = vti.size();
		m_pv = new float[vlen * 3];
		m_pn = new float[nlen * 3];
		m_pt = new float[tlen * 2];
		int idx = 0;
		for (int i=0; i<vlen; i++) {
			m_pv[idx + 0] = vv[vvi[i] * 3 + 0];
			m_pv[idx + 1] = vv[vvi[i] * 3 + 1];
			m_pv[idx + 2] = vv[vvi[i] * 3 + 2];
			idx += 3;
		}
		idx = 0;
		for (int i=0; i<nlen; i++) {
			m_pn[idx + 0] = vn[vni[i] * 3 + 0];
			m_pn[idx + 1] = vn[vni[i] * 3 + 1];
			m_pn[idx + 2] = vn[vni[i] * 3 + 2];
			idx += 3;
		}
		//for (int i=0; i<tlen; i++)   m_pt[i] = vt[vti[i]];
		
		m_nvrts = vlen;
		return true;
	}
	void CObj::unload(void)
	{
		if (m_pv)   { delete[] m_pv;   m_pv = NULL; }
		if (m_pn)   { delete[] m_pn;   m_pn = NULL; }
		if (m_pt)   { delete[] m_pt;   m_pt = NULL; }
		m_nvrts = 0;
	}
	const float* CObj::get_vertex(void) const
	{
		return m_pv;
	}
	const float* CObj::get_normal(void) const
	{
		return m_pn;
	}
	const float* CObj::get_texcoord(void) const
	{
		return m_pt;
	}
	const int CObj::vertex_num(void) const
	{
		return m_nvrts;
	}
	void CObj::build_normal(void)
	{
		assert(false);
		if (m_pn)   { delete[] m_pn;   m_pn = NULL; }
		m_pn = new float[m_nvrts * 3];
		
		int idx = 0;
		float v1[3], v2[3], v3[3], n[3];
		for (int i=0; i<m_nvrts*3; i+=9) {
			v1[0] = m_pv[i + 0];
			v1[1] = m_pv[i + 1];
			v1[2] = m_pv[i + 2];
			
			v2[0] = m_pv[i + 3];
			v2[1] = m_pv[i + 4];
			v2[2] = m_pv[i + 5];
			
			v3[0] = m_pv[i + 6];
			v3[1] = m_pv[i + 7];
			v3[2] = m_pv[i + 8];
			
			calculate_normal(v1, v2, v3, n);
			
			m_pn[idx + 0] = n[0];
			m_pn[idx + 1] = n[1];
			m_pn[idx + 2] = n[2];
			idx += 3;
		}
	}
	void CObj::calculate_normal(float* v1, float* v2, float* v3, float* nor)
	{
		float va[3], vb[3], vr[3], nf;
		
		// vector from v2->v1 and v3->v1
		va[0] = v1[0] - v2[0];
		va[1] = v1[1] - v2[1];
		va[2] = v1[2] - v2[2];
		
		vb[0] = v1[0] - v3[0];
		vb[1] = v1[1] - v3[1];
		vb[2] = v1[2] - v3[2];
		
		// cross product
		vr[0] = va[1] * vb[2] - vb[1] * va[2];
		vr[1] = vb[0] * va[2] - va[0] * vb[2];
		vr[2] = va[0] * vb[1] - vb[0] * va[1];
		
		// normalization factor
		nf = sqrtf( vr[0]*vr[0] + vr[1]*vr[1] + vr[2]*vr[2] );
		
		nor[0] = vr[0] / nf;
		nor[1] = vr[1] / nf;
		nor[2] = vr[2] / nf;
	}
}